/**************************************************************************
 *
 * Copyright 2014 MaeckySoft Ltd., Bludenz, Austria.
 * All Rights Reserved.
 *
 **************************************************************************/

#ifdef __unix__

#include "MOpenGL.h"

#include <math.h>
#include <assert.h>
#include "MTools.h"

#define FLOAT_TO_FIXED(X)   ((X) * 65535.0)

MOpenGL::MOpenGL(int iDebug) : M3DBase(iDebug)
{
	if (iDebug >= MDBG_VERBOSE) std::cout << "(" << __FILE__ << ") <DBG_VERBOSE> " << __PRETTY_FUNCTION__ << " called." << std::endl;

    m_GLiAttr_Pos=0;
    m_GLiAttr_Color=1;
    m_GLiU_Matrix = -1;
    m_GLfView_RotX=0.0;
    m_GLfView_RotY=0.0;
    //ctor
}

void MOpenGL::printMethodInfo(std::string method)
{
	if (m_iDebug == MDBG_ALL) std::cout << "(" << __FILE__ << ") <DBG_ALL> " << method << " called." << std::endl;
}


MOpenGL::~MOpenGL()
{
	if (m_iDebug >= MDBG_VERBOSE) std::cout << "(" << __FILE__ << ") <DBG_VERBOSE> " << __PRETTY_FUNCTION__ << " called." << std::endl;

    //dtor
}

void MOpenGL::incViewRotY(GLfloat roty)
{
	if (m_iDebug >= MDBG_ALL) this->printMethodInfo(__PRETTY_FUNCTION__);

    m_GLfView_RotY += roty;
}

void MOpenGL::incViewRotX(GLfloat rotx)
{
	if (m_iDebug >= MDBG_ALL) this->printMethodInfo(__PRETTY_FUNCTION__);

    m_GLfView_RotX += rotx;
}
void MOpenGL::make_z_rot_matrix(GLfloat angle, GLfloat *m)
{
	if (m_iDebug >= MDBG_ALL) this->printMethodInfo(__PRETTY_FUNCTION__);

   float c = cos(angle * M_PI / 180.0);
   float s = sin(angle * M_PI / 180.0);
   int i;
   for (i = 0; i < 16; i++)
      m[i] = 0.0;
   m[0] = m[5] = m[10] = m[15] = 1.0;

   m[0] = c;
   m[1] = s;
   m[4] = -s;
   m[5] = c;
}

void MOpenGL::make_scale_matrix(GLfloat xs, GLfloat ys, GLfloat zs, GLfloat *m)
{
	if (m_iDebug >= MDBG_ALL) this->printMethodInfo(__PRETTY_FUNCTION__);

    int i;
    for (i = 0; i < 16; i++)
        m[i] = 0.0;
    m[0] = xs;
    m[5] = ys;
    m[10] = zs;
    m[15] = 1.0;
}


void MOpenGL::mul_matrix(GLfloat *prod, const GLfloat *a, const GLfloat *b)
{
	if (m_iDebug >= MDBG_ALL) this->printMethodInfo(__PRETTY_FUNCTION__);

#define A(row,col)  a[(col<<2)+row]
#define B(row,col)  b[(col<<2)+row]
#define P(row,col)  p[(col<<2)+row]
   GLfloat p[16];
   GLint i;
   for (i = 0; i < 4; i++) {
      const GLfloat ai0=A(i,0),  ai1=A(i,1),  ai2=A(i,2),  ai3=A(i,3);
      P(i,0) = ai0 * B(0,0) + ai1 * B(1,0) + ai2 * B(2,0) + ai3 * B(3,0);
      P(i,1) = ai0 * B(0,1) + ai1 * B(1,1) + ai2 * B(2,1) + ai3 * B(3,1);
      P(i,2) = ai0 * B(0,2) + ai1 * B(1,2) + ai2 * B(2,2) + ai3 * B(3,2);
      P(i,3) = ai0 * B(0,3) + ai1 * B(1,3) + ai2 * B(2,3) + ai3 * B(3,3);
   }
   memcpy(prod, p, sizeof(p));
#undef A
#undef B
#undef PROD
}


void MOpenGL::draw(void)
{
//    if (m_iDebug == MDBG_ALL) fprintf(stdout, "(%s) <DBG_ALL> %s called.\n", __FILE__, __PRETTY_FUNCTION__);

   static const GLfloat verts[3][2] = {
      { -1, -1 },
      {  1, -1 },
      {  0,  1 }
   };
   static const GLfloat colors[3][3] = {
      { 1, 0, 0 },
      { 0, 1, 0 },
      { 0, 0, 1 }
   };
   GLfloat mat[16], rot[16], scale[16];

   /* Set modelview/projection matrix */
   make_z_rot_matrix(m_GLfView_RotX, rot);
   make_scale_matrix(0.5, 0.5, 0.5, scale);
   mul_matrix(mat, rot, scale);
   glUniformMatrix4fv(m_GLiU_Matrix, 1, GL_FALSE, mat);

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   {
      glVertexAttribPointer(m_GLiAttr_Pos, 2, GL_FLOAT, GL_FALSE, 0, verts);
      glVertexAttribPointer(m_GLiAttr_Color, 3, GL_FLOAT, GL_FALSE, 0, colors);
      glEnableVertexAttribArray(m_GLiAttr_Pos);
      glEnableVertexAttribArray(m_GLiAttr_Color);

      glDrawArrays(GL_TRIANGLES, 0, 3);

      glDisableVertexAttribArray(m_GLiAttr_Pos);
      glDisableVertexAttribArray(m_GLiAttr_Color);
   }
}


/* new window size or exposure */
void MOpenGL::reshape(int width, int height)
{
    //if (m_iDebug == MDBG_ALL) fprintf(stdout, "(%s) <DBG_ALL> %s called.\n", __FILE__, __PRETTY_FUNCTION__);

    glViewport(0, 0, (GLint) width, (GLint) height);
}


void MOpenGL::create_shaders(void)
{
	if (m_iDebug >= MDBG_ALL) this->printMethodInfo(__PRETTY_FUNCTION__);

    static const char *vertShaderText =
      "uniform mat4 modelviewProjection; \n"
      "attribute vec4 pos; \n"
      "attribute vec4 color; \n"
      "varying vec4 v_color; \n"
      "void main() { \n"
      "   gl_Position = modelviewProjection * pos; \n"
      "   v_color = color; \n"
      "}";

    static const char *fragShaderText =
      "precision mediump float; \n"
      "varying vec4 v_color;\n"
      "void main() { \n"
      "   gl_FragColor = v_color; \n"
      "}";

    GLuint fragShader, vertShader, program;
    GLint stat;

    vertShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertShader, 1, (const char **) &vertShaderText, NULL);
    glCompileShader(vertShader);
    glGetShaderiv(vertShader, GL_COMPILE_STATUS, &stat);
    if (!stat) {
      GLsizei length;
      GLchar log[1024];
      glGetShaderInfoLog(vertShader, 1024, &length, log);
      fprintf(stderr,"(%s) <Info> log: '%s'\n", __FILE__, log);
      fprintf(stderr,"(%s) <Error> vertex shader did not compile! (%i)\n", __FILE__, stat);
      exit(1);
    }

    fragShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragShader, 1, (const char **) &fragShaderText, NULL);
    glCompileShader(fragShader);
    glGetShaderiv(fragShader, GL_COMPILE_STATUS, &stat);
    if (!stat) {
      GLsizei length;
      GLchar log[1024];
      glGetShaderInfoLog(fragShader, 1024, &length, log);
      fprintf(stderr,"(%s) <Info> log: '%s'\n",__FILE__, log);
      fprintf(stderr,"(%s) <Error> fragment shader did not compile (%i) !\n",__FILE__, stat);
      exit(1);
    }

    program = glCreateProgram();
    glAttachShader(program, fragShader);
    glAttachShader(program, vertShader);
    glLinkProgram(program);

    glGetProgramiv(program, GL_LINK_STATUS, &stat);
    if (!stat) {
      char log[1000];
      GLsizei len;
      glGetProgramInfoLog(program, 1000, &len, log);
      fprintf(stderr,"(%s) <Error> linking:\n%s\n",__FILE__, log);
      exit(1);
    }

    glUseProgram(program);

    if (1) {
      /* test setting attrib locations */
      glBindAttribLocation(program, m_GLiAttr_Pos, "pos");
      glBindAttribLocation(program, m_GLiAttr_Color, "color");
      glLinkProgram(program);  /* needed to put attribs into effect */
    }
    else {
      /* test automatic attrib locations */
      m_GLiAttr_Pos = glGetAttribLocation(program, "pos");
      m_GLiAttr_Color = glGetAttribLocation(program, "color");
   }

    m_GLiU_Matrix = glGetUniformLocation(program, "modelviewProjection");

}


void MOpenGL::init(void)
{
	if (m_iDebug >= MDBG_ALL) this->printMethodInfo(__PRETTY_FUNCTION__);

    typedef void (*proc)();

#if 1 /* test code */
   proc p = eglGetProcAddress("glMapBufferOES");
   assert(p);
#endif
   glClearColor(0.0, 0.0, 0.0, 0.0);
   create_shaders();
}

#endif
